package com.smmisia.article.utils;

import lombok.AllArgsConstructor;
import lombok.Data;
import org.springframework.stereotype.Component;

import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.*;

public class LotterySystem {
    public int getPrizeDrawCount() {
        return prizeDrawCount;
    }

    public void setPrizeDrawCount(int count) {
        this.prizeDrawCount = count;
    }

    public int getPointsDrawCount() {
        return pointsDrawCount;
    }

    public void setPointsDrawCount(int count) {
        this.pointsDrawCount = count;
    }

    public List<Prize> getPrizePool() {
        return prizePool;
    }
    // 实物概率表(73-90抽)
    private static final Map<Integer, Double> PRIZE_PROBABILITY_MAP = new TreeMap<>() {{
        put(73, 0.0060);
        put(74, 0.0660);
        put(75, 0.1260);
        put(76, 0.1860);
        put(77, 0.2460);
        put(78, 0.3060);
        put(79, 0.3660);
        put(80, 0.4260);
        put(81, 0.4860);
        put(82, 0.5460);
        put(83, 0.6060);
        put(84, 0.6660);
        put(85, 0.7260);
        put(86, 0.7860);
        put(87, 0.8460);
        put(88, 0.9060);
        put(89, 0.9660);
        put(90, 1.0000);
    }};

    // 积分概率表(1-11抽)
    private static final Map<Integer, Double> POINTS_PROBABILITY_MAP = new TreeMap<>() {{
        put(1, 0.0510);
        put(2, 0.0484);
        put(3, 0.0459);
        put(4, 0.0436);
        put(5, 0.0414);
        put(6, 0.0393);
        put(7, 0.0373);
        put(8, 0.0354);
        put(9, 0.3689);
        put(10, 0.2855);
        put(11, 0.0034);
    }};

    @Data
    @AllArgsConstructor
    public static class Prize implements Serializable {
        private static final long serialVersionUID = 1L;
        private Long id;
        private String name;
        private PrizeType type;
        private int value;
        private LocalDateTime drawTime;

        @Override
        public String toString() {
            return String.format("%s (价值: %d)", name, value);
        }
    }

    public enum PrizeType {
        NONE("谢谢惠顾"),
        POINTS("积分"),
        PHYSICAL("实物");

        private String desc;
        PrizeType(String desc) {
            this.desc = desc;
        }
    }

    @Data
    public static class Statistics {
        private int totalDraws = 0;
        private int physicalPrizeCount = 0;
        private int pointsPrizeCount = 0;
        private int targetPrizeCount = 0;
        private List<Integer> physicalDrawNumbers = new ArrayList<>();
        private List<Integer> pointsDrawNumbers = new ArrayList<>();
        private List<Integer> targetDrawNumbers = new ArrayList<>();
        private Map<String, Integer> prizeDistribution = new HashMap<>();

        public void addPrize(Prize prize, int drawNumber, boolean isTarget) {
            if (prize.getType() == PrizeType.PHYSICAL) {
                physicalPrizeCount++;
                physicalDrawNumbers.add(drawNumber);
                if (isTarget) {
                    targetPrizeCount++;
                    targetDrawNumbers.add(drawNumber);
                }
            } else if (prize.getType() == PrizeType.POINTS) {
                pointsPrizeCount++;
                pointsDrawNumbers.add(drawNumber);
            }
            prizeDistribution.merge(prize.getName(), 1, Integer::sum);
        }

        public double getAveragePhysicalDraws() {
            return physicalPrizeCount == 0 ? 0 :
                    physicalDrawNumbers.stream().mapToDouble(Integer::doubleValue).average().orElse(0);
        }

        public double getAveragePointsDraws() {
            return pointsPrizeCount == 0 ? 0 :
                    pointsDrawNumbers.stream().mapToDouble(Integer::doubleValue).average().orElse(0);
        }

        public double getAverageTargetDraws() {
            return targetPrizeCount == 0 ? 0 :
                    targetDrawNumbers.stream().mapToDouble(Integer::doubleValue).average().orElse(0);
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append("\n抽奖统计:\n");
            sb.append("总抽奖次数: ").append(totalDraws).append("\n");
            sb.append("获得实物次数: ").append(physicalPrizeCount).append("\n");
            sb.append("获得积分次数: ").append(pointsPrizeCount).append("\n");
            sb.append("获得定轨实物次数: ").append(targetPrizeCount).append("\n");
            sb.append("平均多少抽出实物: ").append(String.format("%.2f", getAveragePhysicalDraws())).append("\n");
            sb.append("平均多少抽出积分: ").append(String.format("%.2f", getAveragePointsDraws())).append("\n");
            sb.append("平均多少抽出定轨实物: ").append(String.format("%.2f", getAverageTargetDraws())).append("\n");
            sb.append("奖品分布: ").append(prizeDistribution).append("\n");
            return sb.toString();
        }
    }

    private Statistics stats = new Statistics();
    private int currentDrawCount = 0;
    private int pointsDrawCount = 0;
    private int prizeDrawCount = 0;
    private int targetValue = 0;
    private Prize targetPrize;
    private List<Prize> prizePool = new ArrayList<>();

    public Prize draw() {
        currentDrawCount++;
        pointsDrawCount++;
        prizeDrawCount++;
        stats.totalDraws++;

        // 先判断积分
        double pointsProbability = getPointsProbability();
        if (Math.random() < pointsProbability || pointsDrawCount >= 10) {
            Prize result = drawPoints();
            stats.addPrize(result, pointsDrawCount, false);
            pointsDrawCount = 0;
            return result;
        }

        // 再判断实物
        double prizeProbability = getPrizeProbability();
        if (Math.random() < prizeProbability || prizeDrawCount >= 90) {
            Prize result = drawPrize();
            if (result.getType() == PrizeType.PHYSICAL) {
                boolean isTarget = result == targetPrize;
                stats.addPrize(result, prizeDrawCount, isTarget);
                prizeDrawCount = 0;
            }
            return result;
        }

        return new Prize(0L, "谢谢惠顾", PrizeType.NONE, 0,LocalDateTime.now());
    }

    private double getPointsProbability() {
        for (Map.Entry<Integer, Double> entry : POINTS_PROBABILITY_MAP.entrySet()) {
            if (pointsDrawCount <= entry.getKey()) {
                return entry.getValue();
            }
        }
        return 0.0034;
    }

    private double getPrizeProbability() {
        for (Map.Entry<Integer, Double> entry : PRIZE_PROBABILITY_MAP.entrySet()) {
            if (prizeDrawCount <= entry.getKey()) {
                return entry.getValue();
            }
        }
        return 1.0;
    }

    private Prize drawPoints() {
        int points;
        double random = Math.random();
        if (random < 0.3) {
            points = (int)(Math.random() * 400) + 600; // 600-1000
        } else if (random < 0.6) {
            points = (int)(Math.random() * 300) + 300; // 300-600
        } else {
            points = (int)(Math.random() * 200) + 100; // 100-300
        }
        return new Prize(-1L, points + "积分", PrizeType.POINTS, points,LocalDateTime.now());
    }

    private Prize drawPrize() {
        if (targetValue >= 2) {
            targetValue = 0;
            return targetPrize;
        }

        if (Math.random() < 0.5 && targetPrize != null) {
            targetValue = 0;
            return targetPrize;
        } else {
            targetValue++;
            return getRandomPrize();
        }
    }

    private Prize getRandomPrize() {
        if (prizePool.isEmpty()) {
            return new Prize(0L, "普通实物", PrizeType.PHYSICAL, 100,LocalDateTime.now());
        }
        int index = (int)(Math.random() * prizePool.size());
        return prizePool.get(index);
    }

    public void addToPrizePool(Prize prize) {
        if (prize.getType() != PrizeType.PHYSICAL) {
            throw new IllegalArgumentException("只能添加实物奖品到奖池");
        }
        prizePool.add(prize);
    }

    public void setTargetPrize(Prize prize) {
        if (prize.getType() != PrizeType.PHYSICAL) {
            throw new IllegalArgumentException("只能定轨实物奖品");
        }
        this.targetPrize = prize;
    }

    public Statistics getStatistics() {
        return stats;
    }

    public void simulateDraws(int times) {
        System.out.println("开始模拟" + times + "次抽奖...\n");
        for (int i = 0; i < times; i++) {
            Prize result = draw();
            if (result.getType() == PrizeType.NONE) {
                System.out.printf("第%d抽: %s\n", i + 1, result);
            }
            if (result.getType() != PrizeType.NONE) {
                System.out.printf("第%d抽: %s\n", i + 1, result);
            }
        }
        System.out.println(stats.toString());
    }

    public static void main(String[] args) {
        LotterySystem lottery = new LotterySystem();

        // 添加奖池物品
        lottery.addToPrizePool(new Prize(1L, "iPhone 15", PrizeType.PHYSICAL, 5999,LocalDateTime.now()));
        lottery.addToPrizePool(new Prize(2L, "AirPods", PrizeType.PHYSICAL, 1299,LocalDateTime.now()));

        // 设置定轨物品
        lottery.setTargetPrize(new Prize(3L, "限定皮肤", PrizeType.PHYSICAL, 168,LocalDateTime.now()));
        // 模拟1000次抽奖
        lottery.simulateDraws(10);
    }
}